package org.pentaho.di.core.database;

import org.pentaho.di.core.Const;
import org.pentaho.di.core.database.BaseDatabaseMeta;
import org.pentaho.di.core.database.DatabaseInterface;
import org.pentaho.di.core.exception.KettleDatabaseException;
import org.pentaho.di.core.plugins.DatabaseMetaPlugin;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.i18n.BaseMessages;

import java.sql.DatabaseMetaData;
import java.sql.ResultSetMetaData;
import java.util.HashSet;
import java.util.Set;

/**
 * MySQL8 数据库插件，基于MySQLDatabaseMeta改造，改动了使用的JDBC驱动类
 */
@DatabaseMetaPlugin(type = "MySQL8", typeDescription = "MySQL8")
public class MySQL8DatabaseMeta extends BaseDatabaseMeta implements DatabaseInterface {
    private static final Set<String> SHORT_MESSAGE_EXCEPTIONS = new HashSet<>();

    static {
        SHORT_MESSAGE_EXCEPTIONS.add("com.mysql.jdbc.PacketTooBigException");
        SHORT_MESSAGE_EXCEPTIONS.add("com.mysql.jdbc.MysqlDataTruncation");
    }

    public MySQL8DatabaseMeta() {
    }

    public int[] getAccessTypeList() {
        return new int[]{0, 1, 4};
    }

    public int getDefaultDatabasePort() {
        return this.getAccessType() == 0 ? 3306 : -1;
    }

    public String getLimitClause(int nrRows) {
        return " LIMIT " + nrRows;
    }

    public String getSQLQueryFields(String tableName) {
        return "SELECT * FROM " + tableName + " LIMIT 0";
    }

    public String getSQLTableExists(String tablename) {
        return this.getSQLQueryFields(tablename);
    }

    public String getSQLColumnExists(String columnname, String tablename) {
        return this.getSQLQueryColumnFields(columnname, tablename);
    }

    public String getSQLQueryColumnFields(String columnname, String tableName) {
        return "SELECT " + columnname + " FROM " + tableName + " LIMIT 0";
    }

    public int getNotFoundTK(boolean use_autoinc) {
        return this.supportsAutoInc() && use_autoinc ? 1 : super.getNotFoundTK(use_autoinc);
    }

    public String getDriverClass() {
        return this.getAccessType() == 1 ? "sun.jdbc.odbc.JdbcOdbcDriver" : "com.mysql.cj.jdbc.Driver";
    }

    public String getURL(String hostname, String port, String databaseName) {
        if (this.getAccessType() == 1) {
            return "jdbc:odbc:" + databaseName;
        } else {
            return Utils.isEmpty(port) ? "jdbc:mysql://" + hostname + "/" + databaseName : "jdbc:mysql://" + hostname + ":" + port + "/" + databaseName;
        }
    }

    public String getExtraOptionSeparator() {
        return "&";
    }

    public String getExtraOptionIndicator() {
        return "?";
    }

    public boolean supportsTransactions() {
        return false;
    }

    public boolean supportsBitmapIndex() {
        return false;
    }

    public boolean supportsViews() {
        return true;
    }

    public boolean supportsSynonyms() {
        return false;
    }

    public String getAddColumnStatement(String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon) {
        return "ALTER TABLE " + tablename + " ADD " + this.getFieldDefinition(v, tk, pk, use_autoinc, true, false);
    }

    public String getModifyColumnStatement(String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon) {
        return "ALTER TABLE " + tablename + " MODIFY " + this.getFieldDefinition(v, tk, pk, use_autoinc, true, false);
    }

    public String getFieldDefinition(ValueMetaInterface v, String tk, String pk, boolean use_autoinc, boolean add_fieldname, boolean add_cr) {
        String retval = "";
        String fieldname = v.getName();
        int length = v.getLength();
        int precision = v.getPrecision();
        if (add_fieldname) {
            retval = retval + fieldname + " ";
        }

        int type = v.getType();
        switch(type) {
        case 1:
        case 5:
        case 6:
            if (!fieldname.equalsIgnoreCase(tk) && !fieldname.equalsIgnoreCase(pk)) {
                if (precision == 0) {
                    if (length > 9) {
                        if (length < 19) {
                            retval = retval + "BIGINT";
                        } else {
                            retval = retval + "DECIMAL(" + length + ")";
                        }
                    } else {
                        retval = retval + "INT";
                    }
                } else if (length > 15) {
                    retval = retval + "DECIMAL(" + length;
                    if (precision > 0) {
                        retval = retval + ", " + precision;
                    }

                    retval = retval + ")";
                } else {
                    retval = retval + "DOUBLE";
                }
            } else if (use_autoinc) {
                retval = retval + "BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY";
            } else {
                retval = retval + "BIGINT NOT NULL PRIMARY KEY";
            }
            break;
        case 2:
            if (length > 0) {
                if (length == 1) {
                    retval = retval + "CHAR(1)";
                } else if (length < 256) {
                    retval = retval + "VARCHAR(" + length + ")";
                } else if (length < 65536) {
                    retval = retval + "TEXT";
                } else if (length < 16777216) {
                    retval = retval + "MEDIUMTEXT";
                } else {
                    retval = retval + "LONGTEXT";
                }
            } else {
                retval = retval + "TINYTEXT";
            }
            break;
        case 3:
        case 9:
            retval = retval + "DATETIME";
            break;
        case 4:
            if (this.supportsBooleanDataType()) {
                retval = retval + "BOOLEAN";
            } else {
                retval = retval + "CHAR(1)";
            }
            break;
        case 7:
        default:
            retval = retval + " UNKNOWN";
            break;
        case 8:
            retval = retval + "LONGBLOB";
        }

        if (add_cr) {
            retval = retval + Const.CR;
        }

        return retval;
    }

    public String[] getReservedWords() {
        return new String[]{"ADD", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ASENSITIVE", "BEFORE", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOTH", "BY", "CALL", "CASCADE", "CASE", "CHANGE", "CHAR", "CHARACTER", "CHECK", "COLLATE", "COLUMN", "CONDITION", "CONNECTION", "CONSTRAINT", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATABASE", "DATABASES", "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", "DAY_SECOND", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELAYED", "DELETE", "DESC", "DESCRIBE", "DETERMINISTIC", "DISTINCT", "DISTINCTROW", "DIV", "DOUBLE", "DROP", "DUAL", "EACH", "ELSE", "ELSEIF", "ENCLOSED", "ESCAPED", "EXISTS", "EXIT", "EXPLAIN", "FALSE", "FETCH", "FLOAT", "FOR", "FORCE", "FOREIGN", "FROM", "FULLTEXT", "GOTO", "GRANT", "GROUP", "HAVING", "HIGH_PRIORITY", "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", "IF", "IGNORE", "IN", "INDEX", "INFILE", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERVAL", "INTO", "IS", "ITERATE", "JOIN", "KEY", "KEYS", "KILL", "LEADING", "LEAVE", "LEFT", "LIKE", "LIMIT", "LINES", "LOAD", "LOCALTIME", "LOCALTIMESTAMP", "LOCATE", "LOCK", "LONG", "LONGBLOB", "LONGTEXT", "LOOP", "LOW_PRIORITY", "MATCH", "MEDIUMBLOB", "MEDIUMINT", "MEDIUMTEXT", "MIDDLEINT", "MINUTE_MICROSECOND", "MINUTE_SECOND", "MOD", "MODIFIES", "NATURAL", "NOT", "NO_WRITE_TO_BINLOG", "NULL", "NUMERIC", "ON", "OPTIMIZE", "OPTION", "OPTIONALLY", "OR", "ORDER", "OUT", "OUTER", "OUTFILE", "POSITION", "PRECISION", "PRIMARY", "PROCEDURE", "PURGE", "READ", "READS", "REAL", "REFERENCES", "REGEXP", "RENAME", "REPEAT", "REPLACE", "REQUIRE", "RESTRICT", "RETURN", "REVOKE", "RIGHT", "RLIKE", "SCHEMA", "SCHEMAS", "SECOND_MICROSECOND", "SELECT", "SENSITIVE", "SEPARATOR", "SET", "SHOW", "SMALLINT", "SONAME", "SPATIAL", "SPECIFIC", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQL_BIG_RESULT", "SQL_CALC_FOUND_ROWS", "SQL_SMALL_RESULT", "SSL", "STARTING", "STRAIGHT_JOIN", "TABLE", "TERMINATED", "THEN", "TINYBLOB", "TINYINT", "TINYTEXT", "TO", "TRAILING", "TRIGGER", "TRUE", "UNDO", "UNION", "UNIQUE", "UNLOCK", "UNSIGNED", "UPDATE", "USAGE", "USE", "USING", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", "VALUES", "VARBINARY", "VARCHAR", "VARCHARACTER", "VARYING", "WHEN", "WHERE", "WHILE", "WITH", "WRITE", "XOR", "YEAR_MONTH", "ZEROFILL"};
    }

    public String getStartQuote() {
        return "`";
    }

    public String getEndQuote() {
        return "`";
    }

    public String getSQLLockTables(String[] tableNames) {
        String sql = "LOCK TABLES ";

        for(int i = 0; i < tableNames.length; ++i) {
            if (i > 0) {
                sql = sql + ", ";
            }

            sql = sql + tableNames[i] + " WRITE";
        }

        sql = sql + ";" + Const.CR;
        return sql;
    }

    public String getSQLUnlockTables(String[] tableName) {
        return "UNLOCK TABLES";
    }

    public boolean needsToLockAllTables() {
        return true;
    }

    public String getExtraOptionsHelpText() {
        return "http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-configuration-properties.html";
    }

    public String[] getUsedLibraries() {
        return new String[]{"mysql-connector-java-3.1.14-bin.jar"};
    }

    public boolean isSystemTable(String tableName) {
        if (tableName.startsWith("sys")) {
            return true;
        } else {
            return tableName.equals("dtproperties");
        }
    }

    public String getSQLInsertAutoIncUnknownDimensionRow(String schemaTable, String keyField, String versionField) {
        return "insert into " + schemaTable + "(" + keyField + ", " + versionField + ") values (1, 1)";
    }

    public String quoteSQLString(String string) {
        string = string.replaceAll("'", "\\\\'");
        string = string.replaceAll("\\n", "\\\\n");
        string = string.replaceAll("\\r", "\\\\r");
        return "'" + string + "'";
    }

    public boolean isMySQLVariant() {
        return true;
    }

    public boolean releaseSavepoint() {
        return false;
    }

    public boolean supportsErrorHandlingOnBatchUpdates() {
        return true;
    }

    public boolean isRequiringTransactionsOnQueries() {
        return false;
    }

    public boolean supportsRepository() {
        return true;
    }

    public boolean fullExceptionLog(Exception e) {
        Throwable cause = e == null ? null : e.getCause();
        return cause == null || !SHORT_MESSAGE_EXCEPTIONS.contains(cause.getClass().getName());
    }

    public void addDefaultOptions() {
        this.addExtraOption(this.getPluginId(), "defaultFetchSize", "500");
        this.addExtraOption(this.getPluginId(), "useCursorFetch", "true");
    }

    public String getLegacyColumnName(DatabaseMetaData dbMetaData, ResultSetMetaData rsMetaData, int index) throws KettleDatabaseException {
        if (dbMetaData == null) {
            throw new KettleDatabaseException("Please provide a valid DatabaseMetaData object");
        } else if (rsMetaData == null) {
            throw new KettleDatabaseException("Please provide a valid ResultSetMetaData object");
        } else {
            try {
                return dbMetaData.getDriverMajorVersion() > 3 ? rsMetaData.getColumnLabel(index) : rsMetaData.getColumnName(index);
            } catch (Exception var5) {
                throw new KettleDatabaseException("Something unexpected went wrong trying to get the legacy column name");
            }
        }
    }
}
